﻿#define _CRT_SECURE_NO_WARNINGS 1

//指针变量的使用
//#include<stdio.h>
//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	printf("%p\n", &a);
//	printf("%p\n", pa);
//	return 0;
//}

//指针的解引用
//#include <stdio.h>
//int main()
//{
//	int a = 10;
//	int b = 10;
//	int* pa = &a;
//	*pa = 9;	//也就是将a的值修改为9，同时地址也发生了变化
//	printf("10 = %p\n", &b);
//	printf("9  = %p\n", pa);
//	return 0;
//}

//注意：指针变量的⼤⼩和类型是⽆关的，只要指针类型的变量，在相同的平台下，⼤⼩都是相同的。
//指针类型的不同，在其解引用的时候所访问的字节就不同。

//指针 +- 整数

//#include <stdio.h>
//int main()
//{
//	int n = 10;
//	int* p = &n;
//	char m = 'a';
//	char* pa = &m;
//	//我们要清楚的记得int为指针类型，，*说明这个变量为指针变量，而p为指针变量。
//	printf("%p\n", &n);
//	printf("%p\n", p);
//	printf("%p\n", p+1); //注意：这里的地址增加了4个字节是因为它是整型类型的指数变量。
//	printf("%p\n", p + 2);	//同时，我们注意到指针变量+n，那它增加的字节也就是4n，这里的4是因为数据类型才得到的，如果是char那就是1.
//	printf("%p\n", pa);
//	printf("%p\n", pa+1);
//	
//
//	return 0;
//}


//void* 指针类型，为无指定类型的指针类型
//void* 无法直接进行指针 +- 整数和解引用的运算。

//#include <stdio.h>
//int main()
//{
//	int a = 10;
//	//这里我要记得：指针变量所取得是某地址。
//	int* pa = &a;
//	//如果是不同a的类型的指针变量可以将a的地址赋予pa指针变量吗？
//	//char* ba = &a;
//	printf("int*pa = %p\n", pa);
//	//printf("char*ba = %p\n", ba);//我们发现，在错误列表中出现了：“初始化”: 从“int *”到“char *”的类型不兼容 的警告。
//	//如果我们使用void*
//	void* ca = &a;
//	printf("void* ca = %p\n", ca);//我们就会发现没有类似char*的警告
//	//所以在不确定一些指针变量的类型时，可以使用void*
//		
//	return 0;
//}


//const修饰指针变量
//这里的const也分为在*的左边和*的右边，以及在*的两边的三种情况。


//int main()
//{
//	//1、const在*的左边
//	int a = 10;
//	const int* pa = &a;
//	//如果我想修改pa的值
//	//*pa = 9;	//我们发现它报错了：表达式必须是可修改的左值
//	//如果我想修改a的值
//	pa = 9;	//我们发现pa是可以修改的。
//	//所以我们得知，const在*左边，限制了*pa修改值，但是pa却是可以修改值的。
//
//	//2、const在*的右边
//	int* const ba = &a;
//	*ba = 9;//我们发现*pa是可以修改的。
//	//ba = 9;//我们发现它报错了：表达式必须是可修改的左值
//	//所以我们得知了：const在*右边，只是限制了ba修改值，而没有限制*ba修改值。
//
//	//那么同理，对于const在*左右两边，我们就可以得知既限制了pa（指针变量本身）修改值，也限制了*pa（指针变量所指向的a(变量))修改值。
//	return 0;
//}

//指针运算
//分为三种：1、指针+-整数 2、指针-指针 3、指针的关系运算

//#include <stdio.h>
//
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;
//	int i = 0;
//	int az = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < az; i++)
//	{
//		//这里就实现了用指针+-整数的运算。
//		printf("%d ", * p + i);	//这里我们注意p是指针变量，指向的是地址值，而*p是解引用，它是指向变量，获取对应的数值。
//	}
//	return 0;
//}

//2、指针-指针
//
//#include <stdio.h>
//int  my_strlen(char* pa)	//这里形参：char* pa == char arr[],如果写成后者形式，需要在子程序内再用指针变量获取数组首元素地址。
//{
//	/*char* pa = arr;	*///获取了数组的首元素的地址
//	char* ppa = pa;	//这里相当于也是存储了数组的首元素的地址
//	while (*ppa != '\0')	//这里的判断是根据数组内为字符串，而字符串是以\0作为结束的标志的。
//	{
//		ppa++;
//	}
//	return ppa - pa;	//这里就运用了指针-指针的运算,也就是地址-地址，得到确切的值。
//}
//
//int main()
//{
//	
//	char arr[] = "abc";
//	printf("%d ", my_strlen(arr));
//	return 0;
//}

//3、指针的关系运算

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* pa = arr;
//	int az = sizeof(arr) / sizeof(arr[0]);
//	int i = 0;
//	while (pa < arr + az)	//这里的arr+az，也就是arr[az]->对应数组下标的元素，在次数组中，这个表示为数组中的最后一个元素，这里也就运用了指针的关系运算。
//	{
//		printf("%d ", *pa);
//		pa++;
//	}
//	
//	return 0;
//}

//野指针的成因：1、指针变量未初始化 2、指针变量发生越界 3、指针变量指向的空间遭到释放（例如在子程序中得出的值要输出时）

//1、指针未初始化
//#include <stdio.h>
//int main()
//{
//	int* p;	//这里就会发生C4700报错：使用了未初始化的局部变量“p”，p也就成了我们所说的野指针
//	*p = 1;
//	return 0;
//}

//2、指针发生越界
//#include <stdio.h>
//int main()
//{
//	int arr[] = { 1,2,3 };
//	int* pa = arr;
//	int az = sizeof(arr) / sizeof(arr[0]);
//	int i = 0;
//	for (i = 0; i <= az; i++)	//这里我们会发现，我们设置了 i <= az(3)，那么在它输出完下标为2的数组的时候，当数组下标为3的时候就已经发生了越界，所以会输出第四个值也就是不确定的值。
//	{
//		printf("%d ", *pa);
//		pa++;
//	}
//	return 0;
//}

//3、指针指向的空间释放
//#include <stdio.h>
//
//int* test()
//{
//	int n = 100;
//	return &n;	//我们注意到，当这个子程序结束返回值时，&n所处的内存空间就被销毁了，所以返回出去时，pa所接收的&n是未知的，所以pa就变成了野指针。
//}
//
//int main()
//{
//	//将子程序内的地址返回给主函数的指针变量。
//	int* pa = test();
//	printf("%d\n", *pa);
//	return 0;
//}

//规避野指针的方法：1、将指针初始化 2、小心指针越界 3、指针变量不再使用或是不知道用途时，及时置空NULL，也可以在使用前检测其有效性 4、避免返回的是局部变量的地址。

//这里讲一下3、指针变量不再使用或是不知道用途时，及时置空NULL，也可以在使用前检测其有效性
//#include <stdio.h>
//#include <assert.h>		//这是assert()函数需要使用到的头文件
//int main()
//{
//	//1、不知道或者不使用这个指针变量时，将其置空
//	//int* pa = NULL;
//	//2、在使用前用assert()函数检测其有效性。但一般都建议如果无误时，就将其注释，因为会增加运行时间。
//	//assert(pa != NULL);
//
//	int a = 10;
//	int* ba = &a;
//	assert(ba != NULL);
//	*ba = 10;
//	printf("%d ", *ba);
//	
//
//
//	return 0;
//}


//指针的使用和传址调用

//1、指针的使用
//#include <stdio.h>
//#include <string.h>
//#include <assert.h>
//
//int my_strlen(const char* pa)	//在*左边加一个const，防止修改*pa的值
//{
//	int count = 0;	//计算字符串长度
//	assert(pa != NULL);
//	while (*pa)	//之所以直接用*pa所指向的变量来判断，是因为字符串中以\0作为结束的标志，同时它也是0；像这样的0还有三个：1、数值0；2、‘0’字符0；3、NULL也是0
//	{
//		pa++;	//让指针变量读取下一个数组下标的值/字符。
//		count++;
//	}
//	return count;
//
//}
//
//int main()
//{
//	char arr[] = "abcd";	//这里我还得注意：由于字符串是用\0来结束的，所以，当数组有字符串的时候，数组的大小就需要计算上\0，从而对于这里的数组大小就需要5
//	//我们一般使用strlen()函数来计算字符串的长度
//	/*size_t a = strlen(arr);
//	printf("%zd\n", a);*/
//
//	//现在我们自己设计一个函数来计算这个字符串的长度。
//	int len = my_strlen(arr);
//	printf("%d\n", len);
//	return 0;
//}

//2、传址调用
//写⼀个函数，交换两个整型变量的值
//#include <stdio.h>
//void change(int* a, int* b)
//{
//	int tmp = *a;
//	*a = *b;
//	*b = tmp;
//}
//
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	printf("a = %d b = %d \n", a, b);
//	//change(a, b);	//当我们运行时，却发现a和b的值完全没有交换；经过调试，我们发现，子程序内的a、b值完全没有传出来到实参，所以才导致a和b没有交换。
//	//当我们回想：之前说过，一个变量在子程序内执行完后，其内存空间会销毁；所以这一次我们将实参的地址传给形参。
//	change(&a, &b);	//这时我们就发现，a和b的值进行了交换。
//	printf("a = %d b = %d \n", a, b);
//	return 0;
//}